﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Collections.ObjectModel;

namespace ClimateControl.Infrastructure.Service
{
    public class CallbackManager
    {
        #region Fields

        private IList<IClimateInfo> callbacks;
        private readonly object thisLock = new object();

        #endregion Fields

        #region Constructor

        public CallbackManager()
        {
            this.callbacks = new List<IClimateInfo>();
        }

        #endregion Constructor

        #region Public Methods

        public void AddCallback(IClimateInfo callback)
        {
            var comObject = callback as ICommunicationObject;
            if (callback != null)
            {
                comObject.Faulted += OnClosedOrFaulted;
                comObject.Closed += OnClosedOrFaulted;

                lock (thisLock)
                {
                    if (!callbacks.Contains(callback))
                        callbacks.Add(callback);
                }
            }
        }

        public void InvokeVotesChangedCallbacks(IEnumerable<Vote> votes)
        {
            InvokeCallbacks("Votes", votes);
        }

        public void InvokeIsAirConOnChangedCallbacks(bool isAirConOn)
        {
            InvokeCallbacks("IsAirConOn", isAirConOn);
        }

        public void InvokeTemperatureChangedCallbacks(int temperature)
        {
            InvokeCallbacks("Temperature", temperature);
        }

        public void InvokeVoteFrequencyChangedCallbacks(double voteFrequency)
        {
            InvokeCallbacks("VoteFrequency", voteFrequency);
        }

        public void InvokeVoteExpiryChangedCallbacks(int voteExpiry)
        {
            InvokeCallbacks("VoteExpiry", voteExpiry);
        }

        public void InvokeIsBigFanOnChangedCallbacks(bool isBigFanOn)
        {
            InvokeCallbacks("IsBigFanOn", isBigFanOn);
        }

        public void InvokeUpdateFrequencyChangedCallbacks(int updateFrequency)
        {
            InvokeCallbacks("UpdateFrequency", updateFrequency);
        }

        #endregion Public Methods

        #region Private Methods

        private void OnClosedOrFaulted(object sender, EventArgs e)
        {
            var callback = sender as IClimateInfo;
            if (callback != null)
                RemoveCallbacks(callback);
        }

        private void RemoveCallbacks(params IClimateInfo[] callbacksToRemove)
        {
            foreach (IClimateInfo callback in callbacksToRemove.Where(c => c != null))
            {
                var comObject = callback as ICommunicationObject;
                if (comObject != null)
                {
                    comObject.Abort();
                    lock (thisLock)
                    {
                        callbacks.Remove(callback);
                    }
                }
            }
        }

        private IEnumerable<IClimateInfo> GetCallbacksToCall()
        {
            lock (thisLock)
            {
                callbacks = callbacks.Where(IsValidCallback).ToList();
                return callbacks;
            }
        }

        private bool IsValidCallback(IClimateInfo callback)
        {
            var comObject = callback as ICommunicationObject;
            return callback != null
                && comObject != null
                && comObject.State != CommunicationState.Faulted
                && comObject.State != CommunicationState.Closed;
        }

        private void InvokeCallbacks(string callbackProperty, object arg)
        {
            var property = typeof(IClimateInfo).GetProperty(callbackProperty);

            if (property == null)
                return;

            var callbacksToMessage = GetCallbacksToCall();

            foreach (IClimateInfo callback in callbacksToMessage)
            {
                new Thread(() =>
                {
                    try
                    {
                        property.SetValue(callback, arg, null);
                    }
                    catch (Exception)
                    {
                        RemoveCallbacks(callback);
                    }
                }).Start();
            };
        }

        #endregion Private Methods
    }
}
